// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package server

import (
	"fmt"

	"apiote.xyz/p/szczanieckiej/config"
	"apiote.xyz/p/szczanieckiej/traffic"

	"net/http"
	"net/url"
	"strings"

	"git.sr.ht/~sircmpwn/go-bare"
	"golang.org/x/text/language"
)

type AbstractHandlerVars interface {
	getWriter() http.ResponseWriter
	getRequest() *http.Request
	getTraffic() *traffic.Traffic
	getConfig() config.Config
	getAccept() map[uint]struct{}
	getFeedName() string

	getPath() []string
	setPath([]string)
	getVersionCode() traffic.Validity
	setVersionCode(traffic.Validity)
	getContext() traffic.Context
	setContext(traffic.Context)
	getAcceptLanguage() string
	setAcceptLanguage(string)
	getPreferredLanguages() []language.Tag
	setPreferredLanguages([]language.Tag)

	getResponse() any
	setResponse(any)
	getResponseBytes() []byte
	setResponseBytes([]byte)
}

type HandlerVars struct {
	w http.ResponseWriter
	r *http.Request
	t *traffic.Traffic
	c config.Config
	a map[uint]struct{}
	f string

	path               []string
	versionCode        traffic.Validity
	context            traffic.Context
	acceptLanguage     string
	preferredLanguages []language.Tag

	response      any
	responseBytes []byte
}

func (v HandlerVars) getWriter() http.ResponseWriter {
	return v.w
}
func (v HandlerVars) getRequest() *http.Request {
	return v.r
}
func (v HandlerVars) getTraffic() *traffic.Traffic {
	return v.t
}
func (v HandlerVars) getConfig() config.Config {
	return v.c
}
func (v HandlerVars) getAccept() map[uint]struct{} {
	return v.a
}
func (v HandlerVars) getFeedName() string {
	return v.f
}
func (v HandlerVars) getPath() []string {
	return v.path
}
func (v *HandlerVars) setPath(p []string) {
	v.path = p
}
func (v HandlerVars) getVersionCode() traffic.Validity {
	return v.versionCode
}
func (v *HandlerVars) setVersionCode(c traffic.Validity) {
	v.versionCode = c
}
func (v HandlerVars) getContext() traffic.Context {
	return v.context
}
func (v *HandlerVars) setContext(c traffic.Context) {
	v.context = c
}
func (v HandlerVars) getAcceptLanguage() string {
	return v.acceptLanguage
}
func (v *HandlerVars) setAcceptLanguage(l string) {
	v.acceptLanguage = l
}
func (v HandlerVars) getPreferredLanguages() []language.Tag {
	return v.preferredLanguages
}
func (v *HandlerVars) setPreferredLanguages(t []language.Tag) {
	v.preferredLanguages = t
}
func (v HandlerVars) getResponse() any {
	return v.response
}
func (v *HandlerVars) setResponse(r any) {
	v.response = r
}
func (v HandlerVars) getResponseBytes() []byte {
	return v.responseBytes
}
func (v *HandlerVars) setResponseBytes(b []byte) {
	v.responseBytes = b
}

func splitPath(v AbstractHandlerVars) (AbstractHandlerVars, error) {
	escapedPath := strings.Split(v.getRequest().URL.EscapedPath()[1:], "/")
	path := make([]string, len(escapedPath))
	var err error
	for i, segment := range escapedPath {
		path[i], err = url.PathUnescape(segment)
		if err != nil {
			return v, fmt.Errorf("while unescaping path segment ‘%s’: %w", segment, err)
		}
	}
	v.setPath(path)
	return v, nil
}

func prepareForm(v AbstractHandlerVars) {
	v.getRequest().ParseForm()
}

func getVersionCode(v AbstractHandlerVars) (AbstractHandlerVars, error) {
	dateString := v.getRequest().Form.Get("date")
	versionCode, _, err := parseDate(dateString, v.getFeedName(), v.getTraffic())
	v.setVersionCode(versionCode)
	return v, err
}

func createContext(v AbstractHandlerVars) AbstractHandlerVars {
	v.setContext(
		traffic.Context{
			DataHome: v.getConfig().FeedsPath,
			FeedID:   v.getFeedName(),
			Version:  v.getVersionCode(),
		},
	)
	return v
}

func getAcceptLanguage(v AbstractHandlerVars) AbstractHandlerVars {
	v.setAcceptLanguage(v.getRequest().Header.Get("Accept-Language"))
	if v.getAcceptLanguage() == "" {
		v.setAcceptLanguage("und")
	}
	return v
}

func parseAcceptLanguage(v AbstractHandlerVars) (AbstractHandlerVars, error) {
	preferredLanguages, _, err := language.ParseAcceptLanguage(v.getAcceptLanguage())
	if err != nil {
		return v, ServerError{
			code:  http.StatusBadRequest,
			field: "Accept-Language",
			value: v.getAcceptLanguage(),
			err:   err,
		}
	}
	v.setPreferredLanguages(preferredLanguages)
	return v, nil
}

func marshalResponse(v AbstractHandlerVars) (AbstractHandlerVars, error) {
	r := v.getResponse()
	bytes, err := bare.Marshal(&r)
	v.setResponseBytes(bytes)
	return v, err
}

func writeResponse(v AbstractHandlerVars) error {
	_, err := v.getWriter().Write(v.getResponseBytes())
	return err
}
